package jfoo.playlist;

import java.applet.Applet;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.EventObject;
import java.util.List;

import javax.swing.AbstractAction;
import javax.swing.CellEditor;
import javax.swing.JComponent;
import javax.swing.JList;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;

import jfoo.detailsTable.DetailsTableModel;
import jfoo.detailsTable.DetailsTableTransferHandler;
import audio.AudioFile;

public class PlaylistList extends JList implements MouseListener,
		MouseMotionListener, KeyListener, CellEditorListener {

	private PlaylistListModel model;

	private DetailsTableModel detailModel;
	DetailsTableTransferHandler detailsTableTransferHandler;

	public PlaylistList(PlaylistListModel model, DetailsTableModel detailModel) {
		super(model);
		this.model = model;
		model.setVue(this);
		this.detailModel = detailModel;

		this.setCellRenderer(new PlaylistListCellRenderer());
		this.setPreferredSize(new Dimension(200, 400));
		this.addMouseListener(this);
		this.addMouseMotionListener(this);
		this.addKeyListener(this);
		this.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
		this.setDragEnabled(true);
		setTransferHandler(new PlaylistListTransferHandler(this));
		initEdition();
	}

	public void setDetailsTableTransferHandler(
			DetailsTableTransferHandler detailsTableTransferHandler) {
		this.detailsTableTransferHandler = detailsTableTransferHandler;
	}

	public DetailsTableTransferHandler getDetailsTableTransferHandler() {
		return detailsTableTransferHandler;
	}

	public void addPlaylist(Playlist playlist) {
		model.addPlaylist(playlist);
		if ((this.getSelectedIndex() == -1) && (model.getSize() > 0)) {
			setPlaylist(model.getSize() - 1);
		}
	}

	public void removePlaylist(int index) {
		if ((index == 0) && (model.getSize() == 1)) {
			// nothing
		} else if (index == 0) {
			setPlaylist(index + 1);
		} else {
			setPlaylist(index - 1);
		}
		model.removePlaylist(index);
	}

	public PlaylistListModel getPlaylistListModel() {
		return model;
	}

	// public void addToPlaylist(int index, AudioFile audioFile) {
	// if ((index != -1) && (index < model.getSize())) {
	// Playlist p = (Playlist) model.getElementAt(index);
	// p.add(audioFile);
	// if (getSelectedIndex() == index) {
	// detailModel.fireTableDataChanged();
	// }
	// }
	// }

	public void addToPlaylist(List<AudioFile> list, int index) {
		if (index == -1) {
			if (list.size() > 0) {
				AudioFile audioFile = list.get(0);
				String name = audioFile.getFile().getParentFile().getName();
				if (name.equals("")) {
					name = "New Playlist";
				}
				this.addPlaylist(new Playlist(name));
				index = model.getSize() - 1;
			} else {
				return;
			}
		}
		
		if ((index != -1) && (index < model.getSize())) {
			Playlist p = (Playlist) model.getElementAt(index);
			p.getAudioFiles().addAll(list);
			if (getSelectedIndex() == index) {
				detailModel.fireTableDataChanged();
			}
		}
	}

	public void setPlaylist(int index) {
		detailModel.setPlaylist((Playlist) this.getModel().getElementAt(index));
		setSelectedIndex(index);
		ensureIndexIsVisible(index);
	}

	public int containSearchPlaylist() {
		for (int i = 0; i < model.getSize(); i++) {
			Playlist p = (Playlist) model.getElementAt(i);
			if (p.getName().equals(new String("Search"))) {
				return i;
			}
		}
		return -1;
	}

	public void keyPressed(KeyEvent e) {

	}

	public void keyReleased(KeyEvent e) {
		int index = this.getSelectedIndex();
		setPlaylist(index);

	}

	public void keyTyped(KeyEvent e) {
		// TODO Auto-generated method stub

	}

	public void mouseClicked(MouseEvent e) {

		int index = this.locationToIndex(e.getPoint());
		if (this.getModel().getSize() != 0) {
			if ((e.getClickCount() == 2)
					&& (SwingUtilities.isRightMouseButton(e))) {
				addPlaylist(new Playlist("New Playlist"));
			} else if (SwingUtilities.isLeftMouseButton(e)) {
				setPlaylist(index);
			} else if (SwingUtilities.isMiddleMouseButton(e)) {
				removePlaylist(index); // TODO checker que on a pas cliqué plus
				// bas dans le vide
			}
		}
	}

	public void mouseEntered(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	public void mouseExited(MouseEvent e) {
		// TODO Auto-generated method stub

	}

	int from;

	public void mousePressed(MouseEvent e) {
		from = getSelectedIndex();
		if (shouldIgnore(e))
			return;
		Point p = e.getPoint();
		int index = locationToIndex(p);
		// The autoscroller can generate drag events outside the Table's
		// range.

		if (index == -1)
			return;
		if (editCellAt(index, e)) {
			setDispatchComponent(e);
			repostEvent(e);
		} else if (isRequestFocusEnabled()) {
			requestFocus();
		}

	}

	public void mouseReleased(MouseEvent e) {

	}

	public void mouseDragged(MouseEvent m) {
		// int to = getSelectedIndex();
		int to = this.locationToIndex(m.getPoint());
		if (to == from)
			return;
		setSelectedIndex(to);
		Playlist p = (Playlist) model.getPlaylists().remove(from);
		model.getPlaylists().add(to, p);
		from = to;
		repaint();
	}

	public void mouseMoved(MouseEvent m) {

	}

	private Component dispatchComponent;

	private void setDispatchComponent(MouseEvent e) {
		Component editorComponent = getEditorComponent();
		Point p = e.getPoint();
		Point p2 = SwingUtilities.convertPoint(PlaylistList.this, p,
				editorComponent);
		dispatchComponent = SwingUtilities.getDeepestComponentAt(
				editorComponent, p2.x, p2.y);
	}

	private boolean repostEvent(MouseEvent e) {
		// Check for isEditing() in case another event has
		// caused the editor to be removed. See bug #4306499.
		if (dispatchComponent == null || !isEditing()) {
			return false;
		}
		MouseEvent e2 = SwingUtilities.convertMouseEvent(PlaylistList.this, e,
				dispatchComponent);
		dispatchComponent.dispatchEvent(e2);
		return true;
	}

	private boolean shouldIgnore(MouseEvent e) {
		return e.isConsumed()
				|| (!(SwingUtilities.isLeftMouseButton(e) && isEnabled()));
	}

	protected Component editorComp = null;
	protected int editingIndex = -1;
	protected ListCellEditor editor = null;
	private PropertyChangeListener editorRemover = null;

	private void initEdition() {
		JTextField textField = new JTextField();
		textField.setBorder(null);
		textField.setHorizontalAlignment(JTextField.CENTER);
		setListCellEditor(new DefaultListCellEditor(textField));
		getActionMap().put("startEditing", new StartEditingAction()); // NOI18N
		getActionMap().put("cancel", new CancelEditingAction()); // NOI18N
		getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0),
				"startEditing"); // NOI18N
		getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
				KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "cancel"); // NOI18N
		putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); // NOI18N
	}

	public void setListCellEditor(ListCellEditor editor) {
		this.editor = editor;
	}

	public ListCellEditor getListCellEditor() {
		return editor;
	}

	public boolean isEditing() {
		return (editorComp == null) ? false : true;
	}

	public Component getEditorComponent() {
		return editorComp;
	}

	public int getEditingIndex() {
		return editingIndex;
	}

	public Component prepareEditor(int index) {
		Object value = getModel().getElementAt(index);
		boolean isSelected = isSelectedIndex(index);
		Component comp = editor.getListCellEditorComponent(this, value,
				isSelected, index);
		if (comp instanceof JComponent) {
			JComponent jComp = (JComponent) comp;
			if (jComp.getNextFocusableComponent() == null) {
				jComp.setNextFocusableComponent(this);
			}
		}
		return comp;
	}

	public void removeEditor() {
		KeyboardFocusManager.getCurrentKeyboardFocusManager()
				.removePropertyChangeListener("permanentFocusOwner",
						editorRemover); // NOI18N
		editorRemover = null;

		if (editor != null) {
			editor.removeCellEditorListener(this);

			if (editorComp != null) {
				remove(editorComp);
			}

			Rectangle cellRect = getCellBounds(editingIndex, editingIndex);

			editingIndex = -1;
			editorComp = null;

			repaint(cellRect);
		}
	}

	public boolean editCellAt(int index, EventObject e) {
		if (editor != null && !editor.stopCellEditing())
			return false;

		if (index < 0 || index >= getModel().getSize())
			return false;
		if (!isCellEditable(index))
			return false;
		if (editorRemover == null) {
			KeyboardFocusManager fm = KeyboardFocusManager
					.getCurrentKeyboardFocusManager();
			editorRemover = new CellEditorRemover(fm);
			fm.addPropertyChangeListener("permanentFocusOwner", editorRemover); // NOI18N
		}

		if (editor != null && editor.isCellEditable(e)) {
			editorComp = prepareEditor(index);
			if (editorComp == null) {
				removeEditor();
				return false;
			}
			editorComp.setBounds(getCellBounds(index, index));
			add(editorComp);
			editorComp.validate();

			editingIndex = index;
			editor.addCellEditorListener(this);

			return true;
		}
		return false;
	}

	public void removeNotify() {
		KeyboardFocusManager.getCurrentKeyboardFocusManager()
				.removePropertyChangeListener("permanentFocusOwner",
						editorRemover); // NOI18N
		super.removeNotify();
	}

	// This class tracks changes in the keyboard focus state. It is used
	// when the XList is editing to determine when to cancel the edit.
	// If focus switches to a component outside of the XList, but in the
	// same window, this will cancel editing.
	class CellEditorRemover implements PropertyChangeListener {
		KeyboardFocusManager focusManager;

		public CellEditorRemover(KeyboardFocusManager fm) {
			this.focusManager = fm;
		}

		public void propertyChange(PropertyChangeEvent ev) {
			if (!isEditing()
					|| getClientProperty("terminateEditOnFocusLost") != Boolean.TRUE) { // NOI18N
				return;
			}

			Component c = focusManager.getPermanentFocusOwner();
			while (c != null) {
				if (c == PlaylistList.this) {
					// focus remains inside the table
					return;
				} else if ((c instanceof Window)
						|| (c instanceof Applet && c.getParent() == null)) {
					if (c == SwingUtilities.getRoot(PlaylistList.this)) {
						if (!getListCellEditor().stopCellEditing()) {
							getListCellEditor().cancelCellEditing();
						}
					}
					break;
				}
				c = c.getParent();
			}
		}
	}

	/*-------------------------------------------------[ Model Support ]---------------------------------------------------*/

	public boolean isCellEditable(int index) {
		if (getModel() instanceof MutableListModel)
			return ((MutableListModel) getModel()).isCellEditable(index);
		return false;
	}

	public void setValueAt(Object value, int index) {
		((MutableListModel) getModel()).setValueAt(value, index);
	}

	/*-------------------------------------------------[ CellEditorListener ]---------------------------------------------------*/

	public void editingStopped(ChangeEvent e) {
		if (editor != null) {
			Object value = editor.getCellEditorValue();
			setValueAt(value, editingIndex);
			removeEditor();
		}
	}

	public void editingCanceled(ChangeEvent e) {
		removeEditor();
	}

	/*-------------------------------------------------[ Editing Actions]---------------------------------------------------*/

	private static class StartEditingAction extends AbstractAction {
		public void actionPerformed(ActionEvent e) {
			PlaylistList list = (PlaylistList) e.getSource();
			if (!list.hasFocus()) {
				CellEditor cellEditor = list.getListCellEditor();
				if (cellEditor != null && !cellEditor.stopCellEditing()) {
					return;
				}
				list.requestFocus();
				return;
			}
			ListSelectionModel rsm = list.getSelectionModel();
			int anchorRow = rsm.getAnchorSelectionIndex();
			list.editCellAt(anchorRow, null);
			Component editorComp = list.getEditorComponent();
			if (editorComp != null) {
				editorComp.requestFocus();
			}
		}
	}

	private class CancelEditingAction extends AbstractAction {
		public void actionPerformed(ActionEvent e) {
			PlaylistList list = (PlaylistList) e.getSource();
			list.removeEditor();
		}

		public boolean isEnabled() {
			return isEditing();
		}
	}

}
